using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.IO;
using System.Xml.Serialization;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters;
using System.Runtime.Serialization.Formatters.Binary;
using System.Xml;

using DotNetNuke.Common.Utilities;
using DotNetNuke.Framework;
using System.Reflection;
using System.Web.Caching;

using iFinity.DNN.Modules.CacheMaster.Entities;

namespace iFinity.DNN.Modules.CacheMaster
{
	public class CacheController
	{
		#region Public Methods

		#endregion

        internal static CacheItemInfoCol GetCacheItems(int portalId)
        {
            CacheItemInfoCol cacheItems = new CacheItemInfoCol();

            foreach (DictionaryEntry cacheDictionaryItem in System.Web.HttpContext.Current.Cache)
            {
                CacheItemInfo item = new CacheItemInfo();
                item.CacheKey = cacheDictionaryItem.Key.ToString();
                GetCacheAdvancedValues(ref item);
                GetCacheCollectionValues(ref item, cacheDictionaryItem.Value);
                item.CacheObjectType = cacheDictionaryItem.Value.GetType().ToString();
                //attempt binary serialization
                item.BinarySerializable = false;
                item.BinaryDeSerializable = false;
                try
                {
                    string binSerialized = BinSerialize(cacheDictionaryItem.Value);
                    item.BinarySerializable = true;//it worked, so it is 
                    //item.BInarySerializedData = binSerialized;
                    object value = BinDeserialize(binSerialized);//get data back out again, checks deserializable as well
                    item.BinaryDeSerializable = true;

                }
                catch (Exception)
                {
                    
                }
                cacheItems.Add(item);
            }

            cacheItems.SortByKey();
            return cacheItems;
        }
        internal static CacheItemInfo GetCacheItem(string key)
        {
            CacheItemInfo item = new CacheItemInfo();
            if (key != null)
            {

                object cacheItemValue = System.Web.HttpContext.Current.Cache[key];
                if (cacheItemValue != null)
                {
                    //get advanced data
                    item.CacheObjectType = cacheItemValue.GetType().ToString();
                    item.CacheKey = key;
                    GetCacheAdvancedValues(ref item);
                    GetCacheCollectionValues(ref item, cacheItemValue);

                    //get serialized xml data
                    if (cacheItemValue.GetType() == typeof(string))
                        item.XmlSerializedData = (string)cacheItemValue;
                    else
                    {
                        StringWriter xmlDataWriter = XmlSerialize(cacheItemValue);
                        if (xmlDataWriter != null)
                        {
                            item.XmlSerializedData = System.Web.HttpContext.Current.Server.HtmlEncode(xmlDataWriter.ToString());
                        }
                        else
                        {
                            item.XmlSerializedData = "Contents could not be de-serialized as Xml";
                        }
                    }
                    //attempt binary serialization
                    item.BinarySerializable = false;
                    item.BinaryDeSerializable = false;
                    try
                    {
                        string binSerialized = BinSerialize(cacheItemValue);
                        item.BInarySerializedData = binSerialized;
                        item.BinarySerializable = true;//it worked, so it is 
                        object value = BinDeserialize(binSerialized);//get data back out again, checks deserializable as well
                        item.BinaryDeSerializable = true;

                    }
                    catch (Exception)
                    {
                        
                    }
                    

                }
            }
            return item;
        }
        internal static void ClearCacheItem(string itemCacheKey)
        {
            DataCache.RemoveCache(itemCacheKey);
        }

        private static void GetCacheCollectionValues(ref CacheItemInfo item, object cacheItemValue)
        {
            try 
            { 
                ICollection col; 
                IEnumerable enumerable;
                if (cacheItemValue == null)
                {
                    item.Count = -1;
                }
                else
                {
                    if ((col = cacheItemValue as ICollection) != null)
                    {
                        item.Count = col.Count;
                    }
                    else
                    {
                        if ((enumerable = cacheItemValue as IEnumerable) != null)
                        {
                            int count = 0;
                            foreach (object val in enumerable)
                                count++;
                            item.Count = count;
                        }
                        else
                        {
                            item.Count = -1;
                        }
                    }
                }
            }
            catch (Exception ex) 
            { 
                item.Count = -1; 
                item.Message = "Exception on 'Count':" + ex.Message; 
            } 
            //try
            //{
            //    PropertyInfo countProperty = cacheItemValue.GetType().GetProperty("Count");
            //    if (countProperty != null)
            //    {
            //        int count = (int)countProperty.GetValue(cacheItemValue, null);
            //        item.Count = count;
            //    }
            //    else
            //    {
            //        //poke around for a 'values' property
            //        PropertyInfo valuesProperty = cacheItemValue.GetType().GetProperty("Values");
            //        int valuesCount = -1;
            //        if (valuesProperty != null)
            //        {
            //            object values = valuesProperty.GetValue(cacheItemValue, null);
            //            if (values != null)
            //            {
            //                PropertyInfo valuesCountProperty = values.GetType().GetProperty("Count");
            //                if (countProperty != null)
            //                {
            //                    valuesCount = (int)valuesCountProperty.GetValue(cacheItemValue, null);
            //                }
            //            }
            //        }
            //        if (valuesCount > -1)
            //            item.Count = valuesCount;
            //        else
            //            item.Count = -1;
            //    }
            //}
            //catch (Exception ex)
            //{
            //    item.Count = -1;
            //    item.Message = "Exception on 'Count':" + ex.Message;
            //}
        }

        private static void GetCacheAdvancedValues(ref CacheItemInfo item)
        {

            object cacheEntry = System.Web.HttpContext.Current.Cache.GetType().GetMethod("Get", BindingFlags.Instance | BindingFlags.NonPublic).Invoke(System.Web.HttpContext.Current.Cache, new object[] { item.CacheKey, 1 });
            PropertyInfo utcLastUpdateProperty = cacheEntry.GetType().GetProperty("UtcLastUsageUpdate", BindingFlags.NonPublic | BindingFlags.Instance);
            if (utcLastUpdateProperty != null)
            {
                DateTime utcLastUpdate = (DateTime)utcLastUpdateProperty.GetValue(cacheEntry, null);
                item.UtcLastUpdateDate = utcLastUpdate;
            }
            else
                item.UtcLastUpdateDate = DateTime.MinValue;

            PropertyInfo utcExpiresProperty = cacheEntry.GetType().GetProperty("UtcExpires", BindingFlags.NonPublic | BindingFlags.Instance);
            if (utcExpiresProperty != null)
            {
                DateTime utcExpiresValue = (DateTime)utcExpiresProperty.GetValue(cacheEntry, null);
                item.UtcExpiryDate = utcExpiresValue;
            }
            else
                item.UtcExpiryDate = DateTime.MinValue;

            PropertyInfo utcCreatedProperty = cacheEntry.GetType().GetProperty("UtcCreated", BindingFlags.NonPublic | BindingFlags.Instance);
            if (utcCreatedProperty != null)
            {
                DateTime utcCreatedValue = (DateTime)utcCreatedProperty.GetValue(cacheEntry, null);
                item.UtcCreatedDate = utcCreatedValue;
            }
            else
                item.UtcCreatedDate = DateTime.MinValue;


            PropertyInfo slidingExpirationProperty = cacheEntry.GetType().GetProperty("SlidingExpiration", BindingFlags.NonPublic | BindingFlags.Instance);
            if (slidingExpirationProperty != null)
            {
                TimeSpan slidingExpiration = (TimeSpan)slidingExpirationProperty.GetValue(cacheEntry, null);
                item.SlidingExpiration = slidingExpiration;
            }
            else
                item.SlidingExpiration = new TimeSpan();

            

        }
        /// <summary>
        /// Serilaize object into a memory stream using Binary Serialization
        /// </summary>
        /// <param name="objectToSerialize">object to serialize</param>
        /// <returns></returns>
        internal static MemoryStream BinarySerialize(object objectToSerialize)
        {
            MemoryStream m = new MemoryStream();
            BinaryFormatter b = new BinaryFormatter();

            b.Serialize(m, objectToSerialize);

            return m;
        }
        public static string BinSerialize(object source)
        {
            IFormatter formatter = new BinaryFormatter();
            var stream = new MemoryStream();
            formatter.Serialize(stream, source);
            return Convert.ToBase64String(stream.ToArray());
        }

	    public static object BinDeserialize(string data)
	    {
	        IFormatter formatter = new BinaryFormatter();
	        Byte[] bytes = Convert.FromBase64String(data);
	        var stream = new MemoryStream(bytes);
	        object value = formatter.Deserialize(stream);
	        return value;

	    }

	    /// <summary>
        /// Serialize object into StringWriter using XmlSerialization
        /// </summary>
        /// <param name="objectToSerialize">object to serialize</param>
        /// <returns></returns>
        internal static StringWriter XmlSerialize(object objectToSerialize)
        {
            System.IO.StringWriter writer = null;
            try
            {
                writer = new System.IO.StringWriter();
                if (objectToSerialize.GetType() is IDictionary)
                {
                    SerializeDictionary(writer, (IDictionary)objectToSerialize);
                }
                else
                {
                    if (objectToSerialize.GetType() == typeof(Hashtable))
                    {
                        SerializeHashtable(writer, (Hashtable)objectToSerialize);
                    }
                    else
                    {
                        XmlSerializer serializer = new XmlSerializer(objectToSerialize.GetType());
                        XmlQualifiedName[] dummyNamespaceName = new XmlQualifiedName[1];
                        dummyNamespaceName[0] = new XmlQualifiedName();

                        serializer.Serialize(writer, objectToSerialize, new XmlSerializerNamespaces(dummyNamespaceName));
                    }
                }
            }
            catch (InvalidOperationException)
            {
                //Ignore This can happen when some objects are just not Serializable using XML serialization
                writer = null;
            }
            catch (System.Runtime.Serialization.SerializationException)
            {
                //Ignore. This can happen when storing a set of custom objects in a collection.
                //The XmlSerializer will start to serialize the collection come across the custom objects
                //amd not know what to do. The use of custom serialization attributes will help the serializer.
                writer = null;
            }
            catch (Exception ex)
            {
                string s = ex.Message;
                writer.Write("Contents could not be serialized as Xml.<p class='exception'>Serialization Exception: " + s + "</p>");
                writer = null;
            }

            return writer;
        }
        public static void SerializeDictionary(StringWriter writer, IDictionary dictionary) 
        { 
            List<Entry> entries = new List<Entry>(dictionary.Count); 
            foreach (object key in dictionary.Keys) 
            { 
                entries.Add(new Entry(key, dictionary[key])); 
            } 
            XmlSerializer serializer = new XmlSerializer(typeof(List<Entry>)); 
            serializer.Serialize(writer, entries); 
        }
        public static void SerializeHashtable(StringWriter writer, Hashtable hashtable)
        {
            List<Entry> entries = new List<Entry>(hashtable.Count);
            foreach (object key in hashtable.Keys)
            {
                entries.Add(new Entry(key, hashtable[key]));
            }
            XmlSerializer serializer = new XmlSerializer(typeof(List<Entry>));
            serializer.Serialize(writer, entries);
        }
        public class Entry 
        { 
            public object Key;
            public object Value; 
            public Entry() { } 
            public Entry(object key, object value) { Key = key; Value = value; } 
        }

    }

}